﻿using System.Xml.Serialization;
using System;
using System.Linq;
using System.IO;
using DiagramCreator.Nodes;
using DiagramCreator.Arrows;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Windows.Controls;
using System.Windows;

namespace DiagramCreator.Diagram
{
    public partial class DiagramSheet
    {
        #region - Save / Load-

        public void SaveToFile()
        {
            Microsoft.Win32.SaveFileDialog saveFileDialog = new Microsoft.Win32.SaveFileDialog();
            bool? result = saveFileDialog.ShowDialog();
            if (result.HasValue && result.Value)
            {
                File.WriteAllText(saveFileDialog.FileName,
                    string.Format(@"<Diagram Title=""{0}"">{1}</Diagram>", "TO DO", // TODO: zrobić zapis tytułu
                    GetXmlForNodesAndConnections()));
                NeedsSave = false;
                Title = saveFileDialog.SafeFileName;
            }
        }

        private string GetXmlForNodesAndConnections()
        {
            string xmlForNodes = GetXmlForAllNodes();
            string xmlForConnections = GetXmlForAllConnections();
            return xmlForNodes + xmlForConnections;
        }

        private string GetXmlForAllConnections()
        {
            string xml = string.Empty;
            IEnumerable<GenericConnectionLine> connectionLines = Children.OfType<GenericConnectionLine>();
            foreach (GenericConnectionLine l in connectionLines)
            {
                xml += GetXmlFromGenericConnectionLine(l);
            }
            return xml;
        }

        private string GetXmlFromGenericConnectionLine(GenericConnectionLine l)
        {
            return string.Format(
                @"<{0}>
                  <ParentID>{1}</ParentID>
                  <ChildID>{2}</ChildID>
                  <NotesIDs>{3}</NotesIDs>
                  </{0}>", l.GetType().Name.ToString(), GetID(l.Parent), GetID(l.Child), GetCommaSeparatedNoteIDS(l)
                );
        }

        private string GetCommaSeparatedNoteIDS(GenericConnectionLine l)
        {
            string result = string.Empty;
            if (l is ArrowLine)
            {
                foreach (NoteNode n in ((ArrowLine)l).Notes)
                {
                    result += GetID(n) + ",";
                }
                if (((ArrowLine)l).Notes.Count > 0)
                    result = result.Substring(0, result.Length - 1); // utnij ostatni przecinek
            }
            
            return result;
        }

        private string GetXmlForAllNodes()
        {
            string xml = string.Empty;
            IEnumerable<GenericNode> nodes = Children.OfType<GenericNode>();
            foreach (GenericNode n in nodes)
                xml += GetXmlFromNode(n);
            return xml;
        }

        private string GetXmlFromNode(GenericNode node)
        {
            return string.Format(
            @"<{0}>
                <ID>{6}</ID>
                <W>{1}</W>
                <H>{2}</H>
                <Top>{3}</Top>
                <Left>{4}</Left>                
                <Txt>{5}</Txt>
              </{0}>", 
                     node.GetType().Name, node.Width, node.Height, node.Top, node.Left,
                     node.NodeTextField.Text, GetID(node));
        }

        private Dictionary<int, int> ids = new Dictionary<int, int>();

        private string GetID(GenericNode node)
        {
            if (ids.ContainsKey(node.GetHashCode()))
                return ids[node.GetHashCode()].ToString();
            else
            {
                int newID = (ids.Values.Count > 0 ? ids.Values.Max() : 0) + 1;
                ids.Add(node.GetHashCode(), newID);
                return newID.ToString();
            }
        }

        public void LoadFromFile()
        {
            // pytanie o zapis aktualnego diagramu
            if (Children.OfType<GenericConnectionLine>().Count() > 0)
            {
                if (NeedsSave && MessageBox.Show("Zapisac aktualny diagram?", "Pytanie", MessageBoxButton.YesNo, MessageBoxImage.Question)
                    == MessageBoxResult.Yes)
                {
                    SaveToFile();
                }
            }
            // usuwam wszystko z aktualnego diagramu
            Children.Clear();
            // i zapis:
            Microsoft.Win32.OpenFileDialog openDialog = new Microsoft.Win32.OpenFileDialog();
            bool? result = openDialog.ShowDialog();
            if (result.HasValue && result.Value)
            {
                NodeIDs.Clear();
                NodePositions.Clear();               
                string filePath = openDialog.FileName;
                try
                {
                    string xml = File.ReadAllText(filePath);
                    if (IsValidXml(xml))
                    {
                        GenericNode[] nodes = GetNodesFromXml(xml);
                        GenericConnectionLine[] connections = GetConnectionsFromXml(xml);
                        SetDiagramPropertiesFromXml(xml);
                        foreach (GenericNode n in nodes)
                            PutNodeIntoCurrentDiagram(n);
                        foreach (GenericConnectionLine l in connections)
                            PutConnectionIntoCurrentDiagram(l);                        
                    }
                }
                catch (Exception ex)
                {
                    ShowException(ex);
                }
            }
        }

        private void ShowException(Exception ex)
        {
            System.Windows.MessageBox.Show(ex.StackTrace, ex.Message,
                System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Error);
        }

        private void SetDiagramPropertiesFromXml(string xml)
        {
            // TODO
        }

        private void PutConnectionIntoCurrentDiagram(GenericConnectionLine l)
        {            
            if (l is ConnectionLineForNote)
                ConnectNote(l.Parent, l.Child);
            else
            {
                ArrowLine c = ConnectNodes(l.Parent, l.Child); // jest tworzony nowy obiekt, tracimy notatki linii!!
                // dodanie notatek
                c.Notes.AddRange(((ArrowLine)l).Notes);
                // bug fix: krzywa linia od StartNode
                if (c.Parent is StartNode)
                {
                    c.X1 += 25.0; c.Y1 += 25.0;
                }
            }
        }

        private void PutNodeIntoCurrentDiagram(GenericNode n)
        {                        
            this.Children.Add(n);
            addListenersToNode(n);
            n.SetValue(Canvas.LeftProperty, NodePositions[n].X);
            n.SetValue(Canvas.TopProperty, NodePositions[n].Y);
        }

        string ARROW_XML_BEGIN = string.Format(@"<{0}>", typeof(ArrowLine).Name);
        string ARROW_XML_END = string.Format(@"</{0}>", typeof(ArrowLine).Name);

        string NOTELINE_XML_BEGIN = string.Format(@"<{0}>", typeof(ConnectionLineForNote).Name);
        string NOTELINE_XML_END = string.Format(@"</{0}>", typeof(ConnectionLineForNote).Name);

        private GenericConnectionLine[] GetConnectionsFromXml(string xml)
        {
            List<GenericConnectionLine> result = new List<GenericConnectionLine>();
            // ARROW
            int startAt = 0;
            int foundAt = xml.IndexOf(ARROW_XML_BEGIN, startAt);
            while (foundAt > 0)
            {
                foundAt += ARROW_XML_BEGIN.Length;
                int end = xml.IndexOf(ARROW_XML_END, foundAt);
                if (end == -1) break;
                string ArrowContent = xml.Substring(foundAt, end - foundAt);

                result.Add(GetArrowLineFromXml(ArrowContent));

                startAt = foundAt;
                foundAt = xml.IndexOf(ARROW_XML_BEGIN, startAt);
            }
            // NOTE
            startAt = 0;
            foundAt = xml.IndexOf(NOTELINE_XML_BEGIN, startAt);
            while (foundAt > 0)
            {
                foundAt += NOTELINE_XML_BEGIN.Length;
                int end = xml.IndexOf(NOTELINE_XML_END, foundAt);
                if (end == -1) break;
                string ArrowContent = xml.Substring(foundAt, end - foundAt);

                result.Add(GetNoteLineFromXml(ArrowContent));

                startAt = foundAt;
                foundAt = xml.IndexOf(NOTELINE_XML_END, startAt);
            }
            return result.ToArray();
        }

        private GenericConnectionLine GetNoteLineFromXml(string ArrowContent)
        {
            GenericNode parent = GetParentOfLineFromXml(ArrowContent);
            GenericNode child = GetChildOfLineFromXml(ArrowContent);

            return new ConnectionLineForNote(parent, child);
        }

        private GenericNode GetChildOfLineFromXml(string ArrowContent)
        {
            int ChildID = (int)GetXmlNodeNumericValueFromXml(ArrowContent, "ChildID");
            return NodeIDs[ChildID];
        }

        private GenericNode GetParentOfLineFromXml(string ArrowContent)
        {
            int ChildID = (int)GetXmlNodeNumericValueFromXml(ArrowContent, "ParentID");
            return NodeIDs[ChildID];
        }

        private GenericConnectionLine GetArrowLineFromXml(string ArrowContent)
        {
            GenericNode parent = GetParentOfLineFromXml(ArrowContent);
            GenericNode child = GetChildOfLineFromXml(ArrowContent);
            List<NoteNode> notes = GetNotesOfLineFromXml(ArrowContent);
            ArrowLine result = new ArrowLine(parent, child, this);
            result.Notes.AddRange(notes);
            return result;
        }

        private List<NoteNode> GetNotesOfLineFromXml(string ArrowContent)
        {
            List<NoteNode> result = new List<NoteNode>();
            if (ArrowContent.Contains("NotesIDs"))
            {
                string[] ids = GetXmlNodeValueFromXml(ArrowContent, "NotesIDs").Split(new char[] { ',' });
                if (ids != null)
                    foreach (string id in ids)
                    {
                        if (!string.IsNullOrEmpty(id))
                            result.Add(NodeIDs[int.Parse(id)] as NoteNode);
                    }
            }
            return result;
        }

        string ACTION_NODE_XML_BEGIN = string.Format(@"<{0}>", typeof(ActionNode).Name);
        string ACTION_NODE_XML_END = string.Format(@"</{0}>", typeof(ActionNode).Name);

        string DEC_NODE_XML_BEGIN = string.Format(@"<{0}>", typeof(DecissionNode).Name);
        string DEC_NODE_XML_END = string.Format(@"</{0}>", typeof(DecissionNode).Name);

        string NOTE_NODE_XML_BEGIN = string.Format(@"<{0}>", typeof(NoteNode).Name);
        string NOTE_NODE_XML_END = string.Format(@"</{0}>", typeof(NoteNode).Name);

        string START_NODE_XML_BEGIN = string.Format(@"<{0}>", typeof(StartNode).Name);
        string START_NODE_XML_END = string.Format(@"</{0}>", typeof(StartNode).Name);

        string STOP_NODE_XML_BEGIN = string.Format(@"<{0}>", typeof(StopNode).Name);
        string STOP_NODE_XML_END = string.Format(@"</{0}>", typeof(StopNode).Name);
        private GenericNode[] GetNodesFromXml(string xml)
        {
            List<GenericNode> result = new List<GenericNode>();
            // ACTION NODE
            int startAt = 0;            
            int foundAt = xml.IndexOf(ACTION_NODE_XML_BEGIN, startAt);
            while (foundAt > 0)
            {
                foundAt += ACTION_NODE_XML_BEGIN.Length;
                int end = xml.IndexOf(ACTION_NODE_XML_END, foundAt); if (end == -1) break;
                string NodeContent = xml.Substring(foundAt, end - foundAt);

                result.Add(GetActionNodeFromXml(NodeContent));

                startAt = end + ACTION_NODE_XML_END.Length;
                foundAt = xml.IndexOf(ACTION_NODE_XML_BEGIN, startAt);
            }
            // DECISSION NODE
            startAt = 0;
            foundAt = xml.IndexOf(DEC_NODE_XML_BEGIN, startAt);
            while (foundAt > 0)
            {
                foundAt += DEC_NODE_XML_BEGIN.Length;
                int end = xml.IndexOf(DEC_NODE_XML_END, foundAt); if (end == -1) break;
                string NodeContent = xml.Substring(foundAt, end - foundAt);

                result.Add(GetDecissionNodeFromXml(NodeContent));

                startAt = end + DEC_NODE_XML_END.Length;
                foundAt = xml.IndexOf(DEC_NODE_XML_BEGIN, startAt);
            }
            // NOTE NODE
            startAt = 0;
            foundAt = xml.IndexOf(NOTE_NODE_XML_BEGIN, startAt);
            while (foundAt > 0)
            {
                foundAt += NOTE_NODE_XML_BEGIN.Length;
                int end = xml.IndexOf(NOTE_NODE_XML_END, foundAt); if (end == -1) break;
                string NodeContent = xml.Substring(foundAt, end - foundAt);

                result.Add(GetNoteNodeFromXml(NodeContent));

                startAt = end + NOTE_NODE_XML_END.Length; if (end == -1) break;
                foundAt = xml.IndexOf(NOTE_NODE_XML_BEGIN, startAt);
            }
            // START NODE
            startAt = 0;
            foundAt = xml.IndexOf(START_NODE_XML_BEGIN, startAt);
            while (foundAt > 0)
            {
                foundAt += START_NODE_XML_BEGIN.Length;
                int end = xml.IndexOf(START_NODE_XML_END, foundAt); if (end == -1) break;
                string NodeContent = xml.Substring(foundAt, end - foundAt);

                result.Add(GetStartNodeFromXml(NodeContent));

                startAt = end + START_NODE_XML_END.Length;
                foundAt = xml.IndexOf(START_NODE_XML_BEGIN, startAt);
            }
            // STOP NODE
            startAt = 0;
            foundAt = xml.IndexOf(STOP_NODE_XML_BEGIN, startAt);
            while (foundAt > 0)
            {
                foundAt += STOP_NODE_XML_BEGIN.Length;
                int end = xml.IndexOf(STOP_NODE_XML_END, foundAt); if (end == -1) break;
                string NodeContent = xml.Substring(foundAt, end - foundAt);

                result.Add(GetStopNodeFromXml(NodeContent));

                startAt = end + STOP_NODE_XML_END.Length;
                foundAt = xml.IndexOf(STOP_NODE_XML_BEGIN, startAt);
            }
            return result.ToArray();
        }

        private GenericNode GetStopNodeFromXml(string NodeContent)
        {
            StopNode n = new StopNode();
            SetNodePropertiesFromXml(n, NodeContent);
            return n;
        }

        private GenericNode GetStartNodeFromXml(string NodeContent)
        {
            StartNode n = new StartNode();
            SetNodePropertiesFromXml(n, NodeContent);
            return n;
        }

        private GenericNode GetNoteNodeFromXml(string NodeContent)
        {
            NoteNode n = new NoteNode();
            SetNodePropertiesFromXml(n, NodeContent);
            return n;
        }

        private GenericNode GetActionNodeFromXml(string NodeContent)
        {
            ActionNode n = new ActionNode();
            SetNodePropertiesFromXml(n, NodeContent);
            return n;
        }

        Dictionary<int, GenericNode> NodeIDs = new Dictionary<int, GenericNode>();

        Dictionary<GenericNode, Point> NodePositions = new Dictionary<GenericNode, Point>();

        private void SetNodePropertiesFromXml(GenericNode n, string NodeContent)
        {
            // ID
            int ID = (int)GetXmlNodeNumericValueFromXml(NodeContent, "ID");
            NodeIDs.Add(ID, n);
            // Width           
            n.Width = (int)GetXmlNodeNumericValueFromXml(NodeContent, "W");
            // Height            
            n.Height = (int)GetXmlNodeNumericValueFromXml(NodeContent, "H");
            // Top
            double top = GetXmlNodeNumericValueFromXml(NodeContent, "Top");
            // Left
            double left = GetXmlNodeNumericValueFromXml(NodeContent, "Left");
            Point p = new Point(left, top);
            NodePositions.Add(n, p);
            // Text field
            n.NodeTextField.Text = GetXmlNodeValueFromXml(NodeContent, "Txt");
        }

        private string GetXmlNodeValueFromXml(string Xml, string XmlNode)
        {
            Regex r = new Regex(string.Format(@"<{0}>[^<]*</{0}>", XmlNode));
            Match m = r.Match(Xml);
            if (!m.Success)
                throw new Exception("Invalid XML file.");
            else
                return m.Value.Trim().Substring(2 + XmlNode.Length, m.Value.Trim().Length - 5 - XmlNode.Length * 2);
        }

        private double GetXmlNodeNumericValueFromXml(string Xml, string XmlNode)
        {
            string value = GetXmlNodeValueFromXml(Xml, XmlNode);           
            return double.Parse(value);
        }

        private GenericNode GetDecissionNodeFromXml(string NodeContent)
        {
            DecissionNode n = new DecissionNode();
            SetNodePropertiesFromXml(n, NodeContent);
            return n;
        }

        private bool IsValidXml(string xml)
        {
            if (xml.StartsWith("<Diagram") && xml.EndsWith("</Diagram>"))
            {
                return true;
            }
            return false;
        }

        public void ExportToImageFile()
        {
            
        }

        #endregion
    }
}